import ci from "miniprogram-ci";
import fs from "fs-extra";
import path from "path";
import os from "os";
import { promisified as regedit } from "regedit";
import { toolchain } from "../toolchain.js";
import { sendBuildFailureAlert, sendRobotImage, sendRobotMsg } from "./alert.js";
import { Cmd } from "./Cmd.js";
import wait from "wait";

export interface DevtoolsResult<T> {
    code: number,
    data: T
}

export interface UploadResult {
    success: boolean,
    needLogin: boolean
    error?: string
}

declare interface IWXProjectConfig {
    appid: string,
    projectname: string
}

export class WechatTools {
    public static async getDevtools(): Promise<void> {
        const reks = ['HKCR\\.mgepackage\\DefaultIcon', 'HKCU\\SOFTWARE\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\MuiCache'];
        for (const rek of reks) {
            const values = await regedit.list([rek]);
            if (values[rek].exists) {
                const valueMap = values[rek].values;
                for (const valueName in valueMap) {
                    if (valueName.includes('wechatdevtools.exe')) {
                        toolchain.wechatDevtools = path.join(path.dirname(valueName), 'cli.bat');
                        return;
                    }
                    const v = valueMap[valueName].value;
                    if (typeof(v) === 'string' && v.includes('微信开发者工具.exe')) {
                        toolchain.wechatDevtools = path.join(path.dirname(v), 'cli.bat');
                        return;
                    }
                }
            }
        }
    }

    public static async islogin(): Promise<DevtoolsResult<boolean>> {
        const cmd = new Cmd();
        const code = await cmd.run(toolchain.wechatDevtools, ['islogin']);
        if (code != 0) {
            await sendBuildFailureAlert('获取微信开发者工具登录状态失败: ' + cmd.output);
            process.exit(1);
        } 
        const data = cmd.output.includes('{"login":true}');
        return { code, data };
    }

    public static async login(): Promise<void> {
        const cmd = new Cmd();
        const tmpFile = path.join(os.tmpdir(), 'wxloginqrcodebase64.txt');
        const code = await cmd.run(toolchain.wechatDevtools, ['login', '-f', 'base64', '-o', tmpFile], { expect: 'QR code generated' });
        if (code != 0) {
            await sendBuildFailureAlert('获取微信开发者工具登录二维码失败: ' + cmd.output);
            process.exit(1);
        }
        const qrcodeBase64 = await fs.readFile(tmpFile, 'utf-8');
        await sendRobotImage(qrcodeBase64);
        await sendRobotMsg('text', '微信开发者工具登陆已失效，二维码将在5分钟后过期，请尊敬的teppei扫码登录。\nPS. 闲杂人等禁止扫码，后果自负！', 'teppei');
    }

    public static async preview(projectRoot: string, qrOuput: string, infoOutput: string): Promise<DevtoolsResult<boolean>> {
        const cmd = new Cmd();
        // 预览，二维码转成 base64 并存到文件 qrOuput
        // 预览，并将预览代码包大小等信息存入
        const code = await cmd.run(toolchain.wechatDevtools, ['preview', '--project', projectRoot, '--qr-output', qrOuput, '--qr-format', 'base64', '--info-output', infoOutput]);
        if (code != 0) {
            await sendBuildFailureAlert('[WechatTools] preview failed: ' + cmd.output);
            process.exit(1);
        } 
        const data = cmd.output.includes('√ preview');
        return { code, data };
    }

    public static async upload(projectRoot: string, version: string, desc: string, infoOutput: string): Promise<DevtoolsResult<UploadResult>> {
        const cmd = new Cmd();
        // 上传，并将额外信息存入
        const code = await cmd.run(toolchain.wechatDevtools, ['upload', '--project', projectRoot, '--v', version, '-d', desc, '--info-output', infoOutput]);
        if (code != 0) {
            await sendBuildFailureAlert('[WechatTools] upload failed: ' + cmd.output);
            process.exit(1);
        } 
        const success = cmd.output.includes('√ upload');
        let needLogin = false;
        if (!success) {
            // 检查是否需要重新登录
            if (cmd.output.includes('需要重新登录')) {
                needLogin = true;
                await this.login();
            }
        }
        return { code, data: { success, needLogin } };
    }

    public static async canWorkWithCi(projectPath: string): Promise<boolean> {
        const pcJson = path.join(projectPath, 'project.config.json');
        const pc = await fs.readJSON(pcJson) as IWXProjectConfig;

        const privateKeyPath = path.join(toolchain.params.workSpacePath, `build-tools/cfg/private.${pc.appid}.key`);
        return fs.existsSync(privateKeyPath);
    }

    public static async uploadByCi(projectPath: string, sourceMapSavePath: string, version: string, desc: string): Promise<DevtoolsResult<UploadResult>> {
        // 由于这个愚蠢的ci会使用到npm全局proxy，此处将其关闭
        const cmd = new Cmd();
        const errorCode = await cmd.runNodeModule('npm', ['config', 'delete', 'proxy']);
        console.log('cancel npm proxy, error code:', errorCode);

        const pcJson = path.join(projectPath, 'project.config.json');
        const pc = await fs.readJSON(pcJson) as IWXProjectConfig;

        let error = '';

        const privateKeyPath = path.join(toolchain.params.workSpacePath, `build-tools/cfg/private.${pc.appid}.key`);
        if (!fs.existsSync(privateKeyPath)) {
            console.error('private key not exists, upload failed!');
            return { code: 1, data: { success: false, needLogin: false, error: 'private key not exists' } };
        }

        const project = new ci.Project({
            appid: pc.appid,
            type: 'miniGame',
            projectPath,
            privateKeyPath,
            ignores: ['node_modules/**/*']
        });

        // 先上传
        let success = true;
        const uploadResult = await ci.upload({
            project,
            version,
            desc,
            setting: {
                es6: true
            },
            robot: 1
        }).catch(reason => {
            error = 'upload failed: '+ reason;
            console.error(error);
            success = false;
        });

        console.log('upload result: ', JSON.stringify(uploadResult));

        // 拉取sourcemap
        // https://developers.weixin.qq.com/minigame/dev/devtools/ci.html#%E6%8B%89%E5%8F%96%E6%9C%80%E8%BF%91%E4%B8%8A%E4%BC%A0%E7%89%88%E6%9C%AC%E7%9A%84sourceMap
        await ci.getDevSourceMap({
            project,
            robot: 1,
            sourceMapSavePath
        }).catch((reason) => {
            error = 'getDevSourceMap failed: ' + reason;
            console.error(error);
        });
        
        return { code: 0, data: { success, needLogin: false, error } };
    }

    public static async quit(projectRoot: string): Promise<void> {
        const cmd = new Cmd();
        // 关闭项目时，会有弹窗提示是否阻止；如未阻止，将在 3 秒后关闭
        const closeCode = await cmd.run(toolchain.wechatDevtools, ['close', '--project', projectRoot]);
        if (closeCode != 0) {
            await sendBuildFailureAlert('开发者工具关闭项目失败: ' + cmd.output);
        }
        // 关闭开发者工具时，会有弹窗提示是否阻止；如未阻止，将在 3 秒后关闭
        const quitCode = await cmd.run(toolchain.wechatDevtools, ['quit']);
        if (quitCode != 0) {
            await sendBuildFailureAlert('关闭开发者工具失败: ' + cmd.output);
        }
        // 等待3秒
        await wait(3000);
    }
}
